﻿using System;
using System.Net;
using System.Threading.Tasks;

namespace afirewall.core
{
    using Newtonsoft.Json;
    using oueb.Ceen;
    using SanlieHash;
    using SanlieHash.Fast;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using txcloud.sdk.Lighthouse;

    internal class FirewallModule : IHttpModule
    {
        internal AfirewallAuthMap auth { get; set; }
        internal ConcurrentDictionary<string, CacheBag<string>> cache = new ConcurrentDictionary<string, CacheBag<string>>();
        public int CacheSenconds = 120;
        public FirewallModule(AfirewallAuthMap auth) {
            this.auth = auth;
        }
        public async Task<bool> HandleAsync(IHttpContext context)
        {
            string iptext = string.Empty;
            if (context.Request.Headers.ContainsKey("X-Real-IP")) iptext = context.Request.Headers["X-Real-IP"];
            else if (context.Request.RemoteEndPoint is IPEndPoint ip) iptext = ip.Address.ToString();
            if (!string.IsNullOrEmpty(iptext)) {
                switch (context.Request.Method)
                {
                    case "GET":
                        if (!context.Request.QueryString.ContainsKey("v") || !context.Request.QueryString.ContainsKey("rao"))
                            goto not_auth_request;
                        var _get_rao = context.Request.QueryString["rao"];
                        var _get_v = context.Request.QueryString["v"];
                        if (!uint.TryParse(FastEncode.DecodeMorse(FastEncode.DecodeHex(_get_rao)), out var rao_1))
                            goto not_auth_request;
                        if (!verify_rao(rao_1)) goto not_auth_request;

                        var _get_hash = FastGuoMi.DecryptSM4(_get_v, FastEncode.EncodeHex(FastEncode.EncodeMorse(_get_rao)));
                        if (!CheckFirewall(_get_hash, iptext, auth.LoopFirewallTags(_get_hash))) goto not_auth_request;

                        if(context.Request.QueryString.ContainsKey("rm") && context.Request.QueryString["rm"]=="all")
                        {
                            OpenFirewall(_get_hash, "127.0.0.1", auth.LoopFirewallTags(_get_hash));
                            goto not_auth_request;
                        }
                        await context.Response.WriteAllJsonAsync(":ok");
                        break;
                    case "POST":
                        var reader = new StreamReader(context.Request.Body);
                        var text = await reader.ReadToEndAsync();
                        var post_bag = JsonConvert.DeserializeObject<AuthReqBag>(text);
                        if (post_bag == null) goto not_auth_request;
                        if (!uint.TryParse(FastEncode.DecodeMorse(FastEncode.DecodeHex(post_bag.RAO)), out var rao_2))
                            goto not_auth_request;
                        if (!verify_rao(rao_2)) goto not_auth_request;

                        var _post_hash = FastGuoMi.DecryptSM4(post_bag.Data, FastEncode.EncodeHex(FastEncode.EncodeMorse(post_bag.RAO)));
                        if (FastHash.GetSha3_512(post_bag.RAO + auth.passmaps[_post_hash]) != post_bag.Password) goto not_auth_request;
                        if (!OpenFirewall(_post_hash, iptext, auth.LoopFirewallTags(_post_hash))) goto not_auth_request;
                        await context.Response.WriteAllJsonAsync(":ok");
                        break;
                    default:
                        await context.Response.WriteAllJsonAsync(iptext);
                        break;
                }

                return true;
            not_auth_request:
                await context.Response.WriteAllJsonAsync(":no");
                return true;
            }
            await context.Response.WriteAllJsonAsync(":noip");
            return true;
        }
        internal bool verify_rao(uint rao)
        {
            var now = DateTime.UtcNow;
            var year_snap = (rao >> 24) & 0xf;
            var year = now.Year;
            if (year % 10 != year_snap) year--;
            if (year % 10 != year_snap) return false;
            var _base = new DateTime(year, 5, 20, 0,0,0,DateTimeKind.Utc);
            var _start = _base.AddMinutes((rao & 0xfffff)*5+ ((rao>>20)&0xf));
            var _end = _start.AddMinutes(5);
            if (now > _start && now < _end) return true;
            return false;
        }

        internal bool CheckFirewall(string hash, string ip, IEnumerable<string> tags)
        {
            var now = DateTime.UtcNow;
        start_cache:
            if (cache.TryGetValue(hash, out var bag))
            {
                if (bag.cache_time.AddSeconds(CacheSenconds) >= now)
                    return bag.data == ip;
            }
            else
            {
                bag = CacheBag<string>.New("");
                if (!cache.TryAdd(hash, bag)) goto start_cache;
            }
            var app = new Lighthouse();
            var firewall = app.ReadFirewall();
            var infos = firewall.Rules.Where(t => tags.Contains(t.FirewallRuleDescription));
            if (infos != null)
                if (ip.Contains(":"))
                    foreach (var info in infos)
                    {
                        if (info.Ipv6CidrBlock != ip)
                        {
                            if (bag.ip != info.Ipv6CidrBlock) cache.TryUpdate(hash, CacheBag<string>.New(info.Ipv6CidrBlock), bag);
                            return false;
                        }

                    }
                else
                    foreach (var info in infos)
                    {
                        if (info.CidrBlock != ip)
                        {
                            if (bag.ip != info.CidrBlock) cache.TryUpdate(hash, CacheBag<string>.New(info.CidrBlock), bag);
                            return false;
                        }
                    }

            cache.TryUpdate(hash, CacheBag<string>.New(ip), bag);
            return true;
        }
        internal bool OpenFirewall(string hash, string ip, IEnumerable<string> tags)
        {
            var app = new Lighthouse();
            var firewall = app.ReadFirewall();
            var infos = firewall.Rules.Where(t => tags.Contains(t.FirewallRuleDescription));
            if (infos != null)
            {
                var changed = false;
                if (ip.Contains(":"))
                    foreach (var info in infos)
                    {
                        if (info.Ipv6CidrBlock == ip) continue;
                        info.CidrBlock = null;
                        info.Ipv6CidrBlock = ip;
                        changed = true;
                    }
                else
                    foreach (var info in infos)
                    {
                        if (info.CidrBlock == ip) continue;
                        info.Ipv6CidrBlock = null;
                        info.CidrBlock = ip;
                        changed = true;
                    }
                if (changed)
                    app.UpdateFirewall(firewall);
            }
            if (cache.TryGetValue(hash, out var bag) && bag.ip != ip)
                cache.TryUpdate(hash, CacheBag<string>.New(ip), bag);
            return true;
        }

        internal bool CheckFirewall(string ip, string tag)
        {
            var app = new Lighthouse();
            var firewall = app.ReadFirewall();
            var infos = firewall.Rules.Where(t => tag == t.FirewallRuleDescription);
            if (infos != null)
                foreach (var info in infos)
                    if (info.CidrBlock.Equals(ip))
                        return true;
            return false;
        }
        internal bool OpenFirewall(string ip, string tag)
        {
            var app = new Lighthouse();
            var firewall = app.ReadFirewall();
            var infos = firewall.Rules.Where(t => tag == t.FirewallRuleDescription);
            if (infos != null)
            {
                var changed = false;
                foreach (var info in infos)
                {
                    if (info.CidrBlock.Equals(ip))
                        return true;
                    info.CidrBlock = ip;
                    changed = true;
                }
                if (changed)
                {
                    app.UpdateFirewall(firewall);
                    return true;
                }
            }
            return false;
        }
    }
}
